<template>
  <!-- 三维画布 -->
  <div style="width: 100%; height: 100%; position: relative">
    <div id="three_div" ref="draw" class="draw" />
    <div v-if="loadSuccess" class="loadingBox">
      <div class="progress">
        <span />
      </div>
    </div>
    <div v-if="loadFail" class="loadingBox">
      {{ $t("DEVICE_MODEL_FOUND") }}
    </div>
    <div v-if="!loadSuccess && !loadFail" class="btnbox flex flex-column">
      <span class="btnicon rotate" :class="rotateFlag?'stoprotate':''" :title="rotateTitle" @click="clickBtn('rotate')" />
      <span v-if="model.modelType === 'AOCC'" class="btnicon men" :class="menFlag?'closemen':''" :title="menTitle" @click="clickBtn('men')" />
    </div>
  </div>
</template>

<script>
  import * as THREE from 'three' // 三维
  import TWEEN from '@tweenjs/tween.js'
  import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js' // 控制器
  import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js' // 控制器
  import { FontLoader } from 'three/examples/jsm/loaders/FontLoader.js'
  import { Line2 } from 'three/examples/jsm/lines/Line2.js'
  import { LineMaterial } from 'three/examples/jsm/lines/LineMaterial.js'
  import { LineGeometry } from 'three/examples/jsm/lines/LineGeometry.js'
  import getThemeColor from '@/utils/themesColor'
  const pxPath = require('@/assets/img/environment/px.jpg')
  const nxPath = require('@/assets/img/environment/nx.jpg')
  const pyPath = require('@/assets/img/environment/py.jpg')
  const nyPath = require('@/assets/img/environment/ny.jpg')
  const pzPath = require('@/assets/img/environment/pz.jpg')
  const nzPath = require('@/assets/img/environment/nz.jpg')
  export default {
    props: {
      faceList: {
        type: Array,
        default: () => []
      },
      capacity: {
        type: [Number, String],
        default: 0
      },
      model: {
        type: Object,
        default: () => {}
      },
      modelTypes: {
        type: Object,
        default: () => {}
      }
    },
    data() {
      return {
        // 声明渲染器
        renderer: '',
        // 声明相机
        camera: '',
        // 声明场景
        scene: '',
        // 声明几何体
        geometry: '',
        // 声明材质
        material: '',
        // 声明网格
        mesh: '',
        // 声明相机控制器
        controls: '',
        // 画布大小
        clientWidth: '',
        clientHeight: '',
        // 模型组
        modelGroup: new THREE.Group(),
        // 记住选中的端口
        selectedPort: null,
        progress: 0,
        loadSuccess: true,
        loadFail: false,
        rotateTitle: '停止动画',
        rotateFlag: false,
        aocctypes: {},
        menTitle: '关门',
        menFlag: false
      }
    },
    watch: {
      faceList(val) {
        console.log(val)
      }
    },
    mounted() {
      this.aocctypes = this.modelTypes
      this.setAoccTypes()
      this.init()
    },
    deactivated() {
      this.disposeFn()
    },
    activated() {
      // 初始化动画
      this.animate()
      // 添加事件
      this.addmeth()
      this.rotateAnimation()
    },
    beforeDestroy() {
      this.disposeFn()
      this.removeObj(this.scene)
      this.renderer && this.renderer.dispose()
      this.renderer.forceContextLoss()
      this.renderer.domElement = null
      this.renderer.content = null
      this.renderer = null
      THREE.Cache.clear()
    },
    methods: {
      typeFn(model, keynum, index) {
        const str = model.split(' ')[1].charAt(index)
        if (Object.keys(this.aocctypes).includes(`AFS-${str}-${keynum}`)) {
          this.aocctypes[`AFS-${str}-${keynum}`].push(model)
        } else {
          this.aocctypes[`AFS-A-${keynum}`].push(model)
        }
      },
      setAoccTypes() { // aocc 归类
        const model = this.model.modelName.replace('AOCC', 'AFS')
        const typeMap = {
          'AFS12': [12, 0],
          'AFS24': [24, 0],
          'AFS192': [192, 0],
          'AFS312': [312, 0]
        }
        Object.keys(typeMap).forEach(item => {
          if (model.includes(item)) {
            this.typeFn(model, ...typeMap[item])
          }
        })

        if (model.includes('AFS48')) {
          model.includes('AFS48 M') ? this.typeFn(model, '48M', 3) : this.typeFn(model, 48, 0)
        }
        if (model.includes('AFS96')) {
          if (model.includes('AFS96 S')) {
            const regex = /(L)/
            const flag = model.match(regex)
            if (flag) { // 显示屏
              this.typeFn(model, '96SL', 3)
            } else { // 无显示屏
              this.typeFn(model, '96S', 3)
            }
          } else if (model.includes('AFS96 M')) {
            this.typeFn(model, '96M', 3)
          } else {
            this.typeFn(model, 96, 0)
          }
        }
        if (model.includes('AFS144') || model.includes('AFS148')) {
          (model.includes('AFS144 NA') || model.includes('AFS148 NA')) ? this.typeFn(model, 144, 2) : this.typeFn(model, 144, 0)
        }
        if (model.includes('AFS324')) {
          this.aocctypes[`AFS-A-312`].push(model)
        }
        console.log(this.aocctypes)
      },

      init() {
        // 初始化渲染器
        this.initRenderer()
        // 初始化场景
        this.initScene()
        // 初始化相机
        this.initCamera()
        // 引入模型
        if (this.model.modelType.includes('AOCC')) {
          this.loadAocc()
        } else {
          this.initgltfLoader()
        }
        // 初始化光源
        this.initLight()
        // 初始化动画
        this.animate()
        // 添加事件
        this.addmeth()
      },
      // 初始化渲染器
      initRenderer() {
        // 实例化渲染器
        this.renderer = new THREE.WebGLRenderer({
          antialias: true, // 是否开启抗锯齿
          alpha: true // 是否可以将背景色设置为透明
        })
        // 解决加载gltf格式模型颜色偏差问题
        this.renderer.outputEncoding = THREE.sRGBEncoding
        this.renderer.setPixelRatio(window.devicePixelRatio)
        // 设置渲染区域尺寸
        this.renderer.setSize(this.$refs.draw.offsetWidth, this.$refs.draw.offsetHeight)
        // 告诉渲染器需要阴影效果
        this.renderer.shadowMap.enabled = true
        this.renderer.shadowMap.type = THREE.PCFSoftShadowMap
        // 设置背景色
        this.renderer.setClearColor(0x000000, 0) // 设置背景颜色
        this.$refs.draw.appendChild(this.renderer.domElement)
      },
      // 初始化场景
      initScene() {
        // 实例化场景
        this.scene = new THREE.Scene()
        // 环境贴图
        const textureCube = new THREE.CubeTextureLoader().load([
          pxPath,
          nxPath,
          pyPath,
          nyPath,
          pzPath,
          nzPath
        ])
        textureCube.encoding = THREE.sRGBEncoding // 和renderer.outputEncoding一致
        // 环境贴图纹理对象textureCube作为.environment属性值,影响所有模型
        this.scene.environment = textureCube
        this.scene.fog = new THREE.Fog(getThemeColor('bgColor'), 250, 500)

      // 红线是X轴，绿线是Y轴，蓝线是Z轴
      // var axesHelper = new THREE.AxesHelper(200)
      // this.scene.add(axesHelper)
      },
      // 初始化相机
      initCamera() {
        this.clientWidth = this.$refs.draw.clientWidth
        this.clientHeight = this.$refs.draw.clientHeight
        const k = this.clientWidth / this.clientHeight // 窗口宽高比
        this.camera = new THREE.PerspectiveCamera(45, k, 1, 3000)
        this.camera.position.set(575.6, 517.1, 1888.8)
        this.camera.lookAt(this.scene.position)
        // 创建相机控制器
        this.controls = new OrbitControls(this.camera, this.renderer.domElement)
        this.controls.enablePan = false // 禁用拖拽
        // this.controls.target.set(0, 20, 0) // 与lookAt参数保持一致
        // this.controls.update() // update()函数内会执行camera.lookAt(controls.target)

        new TWEEN.Tween(this.camera.position)
          .to({ x: 57.56, y: 51.71, z: 188.88 }, 3000)
          .start()
          .easing(TWEEN.Easing.Linear.None)// 使用二次缓动函数

        // // 限制缩放范围
        this.controls.maxDistance = 250
        this.controls.minDistance = 120
        // 上下旋转最大值设置
        this.controls.maxPolarAngle = Math.PI / 2 * 0.9
      },
      groundPan(rangeSize, divisions, color, R, RColor) {
        var group = new THREE.Group()
        var gridHelper = new THREE.GridHelper(rangeSize, divisions, color, color)
        group.add(gridHelper)
        // console.log('gridHelper',gridHelper)
        gridHelper.material.depthWrite = false
        gridHelper.renderOrder = -2
        // CircleGeometry圆形平面几何体
        var geometry = new THREE.CircleGeometry(R, 20, 20)
        geometry.rotateX(Math.PI / 2) // 从XOY平面旋转到XOZ平面
        // 可以选择基础网格材质，基础网格材质不受光照影响，和其它场景配合，颜色更稳定，而且节约渲染资源
        var material = new THREE.MeshBasicMaterial({
          color: RColor,
          side: THREE.DoubleSide,
          depthWrite: false
        })
        // 共享材质和几何体数据，批量创建圆点mesh
        var spacing = rangeSize / divisions
        var spacingR = rangeSize / 2
        for (let i = 0; i < divisions; i++) {
          for (let j = 0; j < divisions; j++) {
            var mesh = new THREE.Mesh(geometry, material)
            mesh.renderOrder = -1
            mesh.translateX(-spacingR + i * spacing)
            mesh.translateZ(-spacingR + j * spacing)
            group.add(mesh)
          }
        }
        return group
      },
      // 引入外部模型 gltf
      initgltfLoader() {
        const loader = new GLTFLoader()
        // 引入默认纹理 //this.$config.resUrl +
        console.log(this.model)
        let modelType = this.model.modelType
        if (this.model.modelType.includes('AOCC')) {
          // aocc 下加载对应的afs设备
          const name = this.model.modelName.replace('AOCC', 'AFS')
          Object.keys(this.aocctypes).forEach(key => {
            if (name === '智能光交箱') {
              modelType = 'AFS-A-192'
            } else if (this.aocctypes[key].includes(name)) { // 根据设备型号判断
              modelType = key
            }
          })
        }
        loader.load(
          this.$config.resUrl + '/glb/device3d/' + modelType + '.glb?v=1',
          (gltf) => {
            const model = gltf.scene
            gltf.scene.traverse(function(obj) {
              if (obj.isMesh) {
                // 判断是否是网格模型
                // 重新设置材质的金属度和粗糙度属性
                obj.material.metalness = 0.7 // 金属度
                obj.material.roughness = 1// 表面粗糙度
              }
            })
            const box = new THREE.Box3().setFromObject(model)
            const v3 = new THREE.Vector3()
            // 获得包围盒长宽高尺寸，结果保存在参数三维向量对象v3中
            box.getSize(v3)
            console.log('查看返回的包围盒尺寸', v3)
            const h = v3.y / 2 - 10
            const w = v3.x
            model.position.y = -h
            loader.load(this.$config.resUrl + '/glb/device3d/footer.glb', (glb) => {
              this.loadSuccess = false
              const mesh = glb.scene
              console.log(mesh)
              const box = new THREE.Box3().setFromObject(mesh)
              const v3 = new THREE.Vector3()
              // 获得包围盒长宽高尺寸，结果保存在参数三维向量对象v3中
              box.getSize(v3)
              const fh = v3.y
              const fw = v3.x
              let scale = 1
              const parent = parseFloat((fw - w) / w)
              if (parent > 1) {
                scale = 1
              } else if (parent < 0.5) {
                scale = 0.8
              } else {
                scale = parent
              }
              model.scale.set(scale, scale, scale)
              mesh.position.y = -(h + fh)
              // 添加地面到场景
              const ground = this.groundPan(500, 25, getThemeColor('mainColor'), 0.2, getThemeColor('mainColor'))
              ground.position.y = -10
              this.scene.add(ground)
              // 添加底座到场景
              if (this.model.modelType.includes('AOCC')) {
                const devicePosObj = this.modelGroup.getObjectByName('设备位置')
                // console.log('设备位置-------', devicePosObj.position)
                model.position.copy(devicePosObj.position)
                model.position.y -= h / 2 + 15
                model.position.x -= 1
                mesh.position.y = -(2 + fh)
                mesh.scale.set(2, 2, 2)
              } else {
                // this.modelGroup.position.y = -10
              }
              this.modelGroup.add(mesh)
              // 添加设备到场景
              this.modelGroup.add(model)
              this.scene.add(this.modelGroup)
              // this.drawArcCurve(w, h)
              this.rotateAnimation()
            })
            // 画底部圆弧
          },
          (xhr) => {},
          (err) => {
            console.log(err)
            this.loadSuccess = false
            this.loadFail = true
          }
        )
      },
      loadAocc() {
        const loader = new GLTFLoader()
        loader.load(
          this.$config.resUrl + '/glb/device3d/AOCC.glb?v=2',
          (glb) => {
            const mesh = glb.scene
            console.log('aocc', mesh)
            mesh.traverse(function(obj) {
              if (obj.isMesh) { // 判断是否是网格模型
                // 重新设置材质的金属度和粗糙度属性
                obj.material.metalness = 0.8// 金属度
                obj.material.roughness = 1// 表面粗糙度
              }
            })
            mesh.position.y += 15
            this.modelGroup.position.y -= 30
            this.modelGroup.scale.set(0.5, 0.5, 0.5)
            this.modelGroup.add(mesh)
            const zuoMen = this.modelGroup.getObjectByName('左边门')
            const youMen = this.modelGroup.getObjectByName('右边门')

            zuoMen.closeTween = this.openClose(0, Math.PI / 2, zuoMen)
            zuoMen.openTween = this.openClose(Math.PI / 2, 0, zuoMen)

            youMen.closeTween = this.openClose(0, -Math.PI / 2, youMen)
            youMen.openTween = this.openClose(-Math.PI / 2, 0, youMen)
            this.initgltfLoader()
          }, (xhr) => {},
          (err) => {
            console.log(err)
            this.loadSuccess = false
            this.loadFail = true
          })
      },
      clickBtn(type) { // 旋转按钮
        switch (type) {
        case 'rotate':
          if (!this.rotateFlag) {
            this.rotateFlag = true
            this.rotateTitle = '开启动画'
            this.rotateFrame && cancelAnimationFrame(this.rotateFrame)
            this.rotateFrame = null
          } else {
            this.rotateFlag = false
            this.rotateTitle = '停止动画'
            this.rotateAnimation()
          }
          break
        case 'men':
          this.aoccOpenCloseMen()
          if (!this.menFlag) {
            this.menFlag = true
            this.menTitle = '开门'
          } else {
            this.menFlag = false
            this.rotateTitle = '关门'
          }
          break
        }
      },
      aoccOpenCloseMen() {
        const zuoMen = this.modelGroup.getObjectByName('左边门')
        const youMen = this.modelGroup.getObjectByName('右边门')
        if (!this.menFlag) {
          zuoMen.closeTween.start()
          youMen.closeTween.start()
        } else {
          zuoMen.openTween.start()
          youMen.openTween.start()
        }
      },
      openClose(angle1, angle2, door) {
        const state = {
          angle: angle1
        }
        const tween = new TWEEN.Tween(state)
        tween.to({
          angle: angle2
        }, 1000)
        tween.onUpdate(function() {
          door.rotation.y = state.angle
        })
        return tween
      },
      rotateAnimation() {
        this.modelGroup.rotateY(0.01) // 手机绕y轴旋转
        this.rotateFrame = requestAnimationFrame(this.rotateAnimation)
      },
      drawArcCurve(w, h) {
        // 创建圆弧线条模型
        var geometry = new LineGeometry() // 声明一个几何体对象BufferGeometry
        // 参数：0, 0圆弧坐标原点x，y  100：圆弧半径    0, 2 * Math.PI：圆弧起始角度
        var R = w // 半径
        var arc = new THREE.ArcCurve(
          0,
          0,
          R,
          Math.PI / 2 + Math.PI / 6,
          Math.PI / 2 - Math.PI / 6
        )
        // getPoints是基类Curve的方法，返回一个vector2对象作为元素组成的数组
        var points = arc.getPoints(50) // 分段数50，返回51个顶点
        // setFromPoints方法从points中提取数据改变几何体的顶点位置数据.attributes.position
        const positions = []
        for (let i = 0; i < points.length; i++) {
          positions.push(points[i].x, points[i].y, 0)
        }
        geometry.setPositions(positions)
        // console.log(geometry.attributes.position);
        // 材质对象
        var material = new LineMaterial({
          linewidth: 2,
          color: getThemeColor('mainColor') // 线条颜色
        })
        // 线条模型对象
        material.resolution.set(window.innerWidth, window.innerHeight)
        var line = new Line2(geometry, material)
        line.rotateX(Math.PI / 2) // 绕x轴旋转90度

        var CircleLine = new THREE.Group() // 线模型和720符号父对象
        CircleLine.add(line)
        CircleLine.position.y -= h + 10 // 平移到产品的底部

        var loader = new FontLoader()
        // THREE.FontLoader加载字体
        loader.load('/fonts/helvetiker_bold.typeface.json', function(font) {
          var material = new THREE.MeshLambertMaterial({
            color: getThemeColor('mainColor'),
            side: THREE.DoubleSide
          })
          // .generateShapes()：获得字符'720°'的轮廓顶点坐标
          var Shapes = font.generateShapes('720°', 10) // 10)控制字符大小
          var geometry = new THREE.ShapeGeometry(Shapes) // 通过多个多边形轮廓生成字体
          var textMesh = new THREE.Mesh(geometry, material)
          textMesh.position.z = R
          textMesh.position.x = -12
          CircleLine.add(textMesh)
        })
        this.scene.add(CircleLine)
      },
      // 添加光源
      initLight() {
        const directionalLight1 = new THREE.DirectionalLight(0xffffff, 1)
        directionalLight1.position.set(0, 200, 200)
        this.scene.add(directionalLight1)
        const directionalLight2 = new THREE.DirectionalLight(0xffffff, 1)
        directionalLight2.position.set(-50, -100, -100)
        this.scene.add(directionalLight2)

        const ambient2 = new THREE.AmbientLight(0xffffff, 0.5)
        this.scene.add(ambient2)
      },
      addmeth() {
        // 监听窗口尺寸变化
        window.addEventListener('resize', this.changeSize, false)
        window.addEventListener('dblclick', this.portClick, false)
      },
      // 运行动画
      animate() {
        // console.log(this.camera.position)
        TWEEN.update()
        this._animate = requestAnimationFrame(this.animate.bind(this)) // 循环调用函数
        // 刷新相机控制器
        // this.controls.update()
        this.renderer.render(this.scene, this.camera)
      },
      // 点击端口
      portClick(event) {
        // 保持原事件
        event.preventDefault()
        this.getIntersects(event.layerX, event.layerY, 'dblclick')
      },
      getIntersects(layerX, layerY, type) {
        // 建立射线
        const raycaster = new THREE.Raycaster()
        // // 建立一个空物体
        const mouseVector = new THREE.Vector3()
        const x = (layerX / this.clientWidth) * 2 - 1
        const y = -(layerY / this.clientHeight) * 2 + 1
        mouseVector.set(x, y, 1)
        raycaster.setFromCamera(mouseVector, this.camera)
        raycaster.params.Line.threshold = 0.01
        const intersections = raycaster.intersectObjects(this.scene.children, true)
        let selectedObject = null // 被选中的模型

        if (intersections.length > 0) {
          for (var i = 0; i < intersections.length; i++) {
            // 遍历线相交模型
            if (intersections[i].object instanceof THREE.Mesh) {
              // 取第一个（距离最近）的相交Mesh类型模型
              // 如果要排除地面等参照模型，也可在此处添加判断条件
              selectedObject = intersections[i].object
              break
            }
          }
        }
        if (selectedObject) {
          // console.log(selectedObject)
          this.$emit('showPort', false)
        }
      },
      // 监听尺寸变化
      changeSize() {
        // 重置渲染器输出画布canvas尺寸
        this.renderer.setSize(this.$refs.draw.offsetWidth, this.$refs.draw.offsetHeight)

        this.clientWidth = this.$refs.draw.clientWidth
        this.clientHeight = this.$refs.draw.clientHeight
        const k = this.clientWidth / this.clientHeight // 窗口宽高比
        // 重置相机投影的相关参数
        this.camera.aspect = k
        // 如果相机的一些属性发生了变化，
        // 需要执行updateProjectionMatrix ()方法更新相机的投影矩阵
        this.camera.updateProjectionMatrix()
      },
      disposeFn() {
        window.removeEventListener('resize', this.changeSize)
        window.removeEventListener('dblclick', this.portClick)
        this._animate && cancelAnimationFrame(this._animate)
        this.rotateFrame && cancelAnimationFrame(this.rotateFrame)
      },
      removeObj(obj) {
        let arr = obj.children.filter((x) => x)
        arr.forEach((item) => {
          if (item.children.length) {
            this.removeObj(item)
          } else {
            item.clear()
          }
        })
        obj.clear()
        arr = null
      }
    }
  }
</script>

<style lang="scss">
@import "@/assets/css/mixin.module";
.btnbox{
  position: absolute;
  left:10px;
  top:50%;
  width:40px;
  z-index: 9;
  .btnicon{
    width:40px;
    height:40px;
    border-radius: 50%;
    background-size: 25px;
    background-repeat: no-repeat;
    background-position: center;
    cursor: pointer;
    @include themeify {
      background-color: rgba($color: themed("mainColor"), $alpha: 0.5);
    }
  }
  .rotate{
    margin-bottom: 10px;
    background-image: url('@/assets/img/rotate.png');
    &.stoprotate{
      background-image: url('@/assets/img/rotate_stop.png');
    }
  }
  .men{
    background-image: url('@/assets/img/openmen.png');
    &.closemen{
      background-image: url('@/assets/img/closemen.png');
    }
  }
}

#three_div {
  width: 100%;
  height: 100%;
}

//进度条
.loadingBox {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 1;
  font-size: 18px;
  @include themeify {
    color: themed("textColor");
  }
}
.progress {
  width: 300px;
  height: 17px;
  border-radius: 20px;
  margin: 0 auto;
  position: relative;
  @include themeify {
    background: rgba($color: themed("mainColor"), $alpha: 0.5);
  }
  overflow: hidden;
}
.progress span {
  display: block;
  width: 100%;
  height: 100%;
  border-radius: 20px;
  @include themeify {
    background: themed("mainColor");
  }
  animation: changePosition 3s linear infinite;
  -webkit-animation: changePosition 3s linear infinite;
}
@-webkit-keyframes changePosition {
  0% {
    -webkit-transform: translate(-300px);
  }
  50% {
    -webkit-transform: translate(0);
  }
  100% {
    -webkit-transform: translate(300px);
  }
}
</style>
